﻿using System;
using System.Collections.Generic;
using System.Linq;
using SlimDX.XInput;
using WiimoteLib;
using SlimDX;
using System.Threading;
using System.Reflection;
using System.IO;
using SlimDX.Multimedia;

namespace GamepadDriver
{
	public class EventWrappedWiimote : IDisposable
	{
		#region Constants

		private const int THRESHOLD = 5;

		#endregion

		#region Variables

		private bool _disposed;
		private Wiimote _wiimote;
		private Vector3 _origin;
		private Vector3 _delta;
		private bool _leftDown;
		private bool _rightDown;
		private bool _wheelDown;
		private WiimoteLib.ButtonState _lastButtonState;

		private Dictionary<WiimoteButton, EventHandler<WiimoteEventArgs>> _actions;

		#endregion

		#region Constructors

		public EventWrappedWiimote(Wiimote wiimote, UserIndex userIndex)
		{
			_actions = new Dictionary<WiimoteButton, EventHandler<WiimoteEventArgs>>();

			_wiimote = wiimote;
			_wiimote.Connect();
			_wiimote.SetLEDs((int)Math.Pow(2, (double)userIndex));
			if (_wiimote.WiimoteState.ExtensionType != ExtensionType.BalanceBoard)
			{
				_wiimote.SetReportType(InputReport.IRExtensionAccel, IRSensitivity.Maximum, true);
			}
			_origin = _wiimote.WiimoteState.AccelState.RawValues.ToVector();

			//LEDTest();
			SoundTest();

			UserIndex = userIndex;
			_leftDown = false;
			_rightDown = false;
			_wheelDown = false;
			_lastButtonState = _wiimote.WiimoteState.ButtonState;
			_disposed = false;

			_wiimote.WiimoteChanged += Wiimote_Changed;
			_wiimote.WiimoteExtensionChanged += WiimoteExtension_Changed;
		}

		~EventWrappedWiimote()
		{
			Dispose(false);
		}

		#endregion

		#region Properties

		public UserIndex UserIndex { get; private set; }

		#endregion

		#region Methods

		public void Map(WiimoteButton button, Action<WiimoteState> action)
		{
			if (!_actions.ContainsKey(button))
			{
				_actions.Add(button, new EventHandler<WiimoteEventArgs>((sender, e) => action(e.WiimoteState)));
			}
			else
			{
				_actions[button] = (sender, e) => { _actions[button](this, e); action(e.WiimoteState); };
			}
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (!_disposed)
			{
				if (disposing)
				{
					_wiimote.SetLEDs(0);
					_wiimote.SetRumble(false);
					_wiimote.Disconnect();
					_wiimote.Dispose();
				}
				_disposed = true;
			}

			//base.Dipose(disposing);
		}

		private byte[] mBuff = null;
		private Action WriteReport = null;

		private void SendReport(byte[] data)
		{
			if (mBuff == null)
			{
				var mBuffField = _wiimote.GetType().GetField("mBuff", BindingFlags.NonPublic | BindingFlags.Instance);
				mBuff = (byte[])mBuffField.GetValue(_wiimote);
			}
			if (WriteReport == null)
			{
				var WriteReportMethod = _wiimote.GetType().GetMethod("WriteReport", BindingFlags.NonPublic | BindingFlags.Instance);
				WriteReport = () => WriteReportMethod.Invoke(_wiimote, null);
			}

			Array.Clear(mBuff, 0, 0x16);
			Array.Copy(data, mBuff, data.Length);
			WriteReport();
		}

		private void LEDTest()
		{
			for (int n = 0; n < 256; n++)
			{
				SendReport(new byte[] { 0x11, (byte)n });
				Thread.Sleep(1);
			}
			SendReport(new byte[] { 0x11, 0x00 });
		}

		private const int REGISTER_SOUND_1 = 0x4A20009;
		private const int REGISTER_SOUND_2 = 0x4A20001;
		private const int REGISTER_SOUND_3 = 0x4A20008;

		/// <summary>
		/// This doesn't work yet.
		/// </summary>
		private void SoundTest()
		{
			SendReport(new byte[] { 0x14, 0x04 }); // enable the speaker
			SendReport(new byte[] { 0x19, 0x04 }); // mute the speaker
			_wiimote.WriteData(REGISTER_SOUND_1, 0x01); // write to register 0xA20009

			_wiimote.WriteData(REGISTER_SOUND_2, 0x08); // write to register 0xA20001
			//SendReport(new byte[] { 0x00, 0x40, 0xD0, 0x07, 0x80, 0x00, 0x00 }); // configure for 8-bit sound at half volume, 3000 Hz
			//SendReport(new byte[] { 0x00, 0x40, 0x77, 0x02, 0x80, 0x00, 0x00 }); // configure for 8-bit sound at half volume at 19000 Hz (12000000 / 19000 = 0x0277)
			//SendReport(new byte[] { 0x00, 0x00, 0x50, 0x1C, 0x40, 0x00, 0x00 }); // configure for 4-bit sound at full volume at 3640 Hz (6000000 / 3640 = 0x1C50)
			SendReport(new byte[] { 0x00, 0x00, 0x7C, 0x07, 0x40, 0x00, 0x00 }); // 4-bit 3130hz
			//SendReport(new byte[] { 0x00, 0x00, 0x00, 10 + (byte)SpeakerFreq.FREQ_3130HZ, 0x40, 0x00, 0x00 });
			_wiimote.WriteData(REGISTER_SOUND_3, 0x01); // write to register 0xA20008
			SendReport(new byte[] { 0x19, 0x00 }); // unmute the speaker

			PlaySound(1);
			//PlaySound(2);
			//PlaySound(4);
			//PlaySound(8);

			//PlaySound(261.63); // C4
			//PlaySound(293.66); // D4
			//PlaySound(329.63); // E4
			//PlaySound(349.23); // F4
			//PlaySound(392.00); // G4
			//PlaySound(440.00); // A4
			//PlaySound(493.88); // B4
			//PlaySound(523.25); // C5


			/*
			// Report 18, send 20 bytes, square wave, 1/4 sample rate freq
			for (var t = 0; t < 80; t++)
				SendReport(new byte[] { 0x18, 20 << 3, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33 });
			
			// Report 18, send 20 bytes, square wave, 1/2 sample rate freq
			for (var t = 0; t < 80; t++)
				SendReport(new byte[] { 0x18, 20 << 3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3 });

			// Report 18, send 20 bytes, square wave, 1/8 sample rate freq
			for (var t = 0; t < 80; t++)
				SendReport(new byte[] { 0x18, 20 << 3, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33 });
			*/
		}

		private void PlaySound(double hz)
		{
			for (int index = 0; index < 400; index++)
			{
				var bytes = new List<byte>();
				bytes.Add(0x18);
				bytes.Add(20 << 3);
				for (int n = 0; n < 20; n++)
				{
					bytes.Add(GetWave(hz));
				}
				SendReport(bytes.ToArray());
			}
		}

		private static int a = 0xCC;

		//private static FileStream file = File.OpenRead(@"C:\Program Files\Messenger\newemail.wav");
		//private static FileStream file = File.OpenRead(@"C:\Program Files\ImgBurn\Sounds\Error.wav");
		//private static FileStream file = File.OpenRead(@"C:\Program Files\Messenger\online.wav");
		//private static FileStream file = File.OpenRead(@"C:\Program Files\NetMeeting\Blip.wav");
		private static Stream sound = LoadSample();
		private static byte[] samples = null;

		//private static WaveStream wave = new WaveStream(file);

		enum SpeakerFreq
		{
			// (keep in sync with FreqLookup in wiimote.cpp)
			FREQ_NONE = 0,
			// my PC can't keep up with these using bUseHIDwrite, so I haven't
			//  been able to tune them yet
			FREQ_4200HZ = 1,
			FREQ_3920HZ = 2,
			FREQ_3640HZ = 3,
			FREQ_3360HZ = 4,
			// these were tuned until the square-wave was glitch-free on my remote -
			//  may not be exactly right
			FREQ_3130HZ = 5,	// +190
			FREQ_2940HZ = 6,	// +180
			FREQ_2760HZ = 7,	// +150
			FREQ_2610HZ = 8,	// +140
			FREQ_2470HZ = 9,
		};

		private static Stream LoadSample()
		{
			var _signed = true;
			var freq = SpeakerFreq.FREQ_3130HZ;

			//var file = File.OpenRead(@"C:\temp\WiiYourself!_1.15\Demo\1kSine16 (3130).raw");
			var bytes = File.ReadAllBytes(@"C:\temp\WiiYourself!_1.15\Demo\1kSine16 (3130).raw");
			var total_samples = (bytes.Length + 1) / 2; // round up just in case file is corrupt
			samples = new byte[total_samples];

			// ADPCM code, adapted from
			//  http://www.wiindows.org/index.php/Talk:Wiimote#Input.2FOutput_Reports
			int[] index_table = new int[] {
				-1,  -1,  -1,  -1,   2,   4,   6,   8,
				-1,  -1,  -1,  -1,   2,   4,   6,   8
			};
			int[] diff_table = new int[] {
				 1,   3,   5,   7,   9,  11,  13,  15,
				-1,  -3,  -5,  -7,  -9, -11, -13,  15
			};
			int[] step_scale = new int[] {
				230, 230, 230, 230, 307, 409, 512, 614,
				230, 230, 230, 230, 307, 409, 512, 614
			};
			// Encode to ADPCM, on initialization set adpcm_prev_value to 0 and adpcm_step
			//  to 127 (these variables must be preserved across reports)
			int adpcm_prev_value = 0;
			int adpcm_step		 = 127;

			for (var i = 0; i < total_samples; i++)
			{
				// convert to 16bit signed
				int value = samples[i];// (8bit) << 8);// | samples[i]; // dither it?
				if (!_signed)
				{
					value -= 32768;
				}
				// encode:
				int diff = value - adpcm_prev_value;
				int encoded_val = 0;
				if (diff < 0)
				{
					encoded_val |= 8;
					diff = -diff;
				}
				diff = (diff << 2) / adpcm_step;
				if (diff > 7)
				{
					diff = 7;
				}
				encoded_val |= diff;
				adpcm_prev_value += ((adpcm_step * diff_table[encoded_val]) / 8);
				if (adpcm_prev_value > 0x7fff)
				{
					adpcm_prev_value = 0x7fff;
				}
				if (adpcm_prev_value < -0x8000)
				{
					adpcm_prev_value = -0x8000;
				}
				adpcm_step = (adpcm_step * step_scale[encoded_val]) >> 8;
				if (adpcm_step < 127)
				{
					adpcm_step = 127;
				}
				if (adpcm_step > 24567)
				{
					adpcm_step = 24567;
				}
				if ((i & 1) != 0)
				{
					samples[i >> 1] |= (byte)encoded_val;
				}
				else
				{
					samples[i >> 1] |= (byte)(encoded_val << 4);
				}
			}
			return new MemoryStream(samples);
		}

		private byte GetWave(double hz)
		{
			return (byte)sound.ReadByte();

			a = ((DateTime.Now.Ticks / 10000000) % 4) == 0 ? (0xFF - a) : a;
			return (byte)a;


			var seconds = 1; // DateTime.Now.Ticks / 10000000.0;
			//var v = 127.5 * (Math.Sin(a) + 1);
			var v = Math.Sin(hz * Math.PI / 2.0 * seconds); // (3000 / hz) * seconds * Math.PI * 2.0);
			//a += 0.00001; //+= Math.PI / 180.0;
			//return (byte)(v > 0 ? 0xFF : 0x00);
			return (byte)((v + 1) * 127.5);
		}

		#endregion

		#region Event Handlers

		private void Wiimote_Changed(object sender, WiimoteChangedEventArgs e)
		{
			var button = typeof(WiimoteLib.ButtonState).GetFields()
				.Where(x => (bool)x.GetValue(e.WiimoteState.ButtonState))
				.Select(x => (WiimoteButton)Enum.Parse(typeof(WiimoteButton), x.Name))
				.Aggregate((WiimoteButton)0, (x, y) => x |= y);

			if (_actions.ContainsKey(button))
			{
				_actions[button](this, new WiimoteEventArgs(_wiimote));
			}

			_delta = e.WiimoteState.AccelState.RawValues.ToVector() - _origin;
			_delta = MathHelper.IsInRange(-THRESHOLD, THRESHOLD, _delta) ? Vector3.Zero : _delta - THRESHOLD * Vector3.Normalize(_delta);

			if (e.WiimoteState.ButtonState.A && e.WiimoteState.ButtonState.B && !_wheelDown)
			{
				if (_leftDown)
				{
					MouseController.UseButton(MouseButton.Left, ButtonState.Released);
					_leftDown = false;
				}
				if (_rightDown)
				{
					MouseController.UseButton(MouseButton.Right, ButtonState.Released);
					_rightDown = false;
				}

				MouseController.UseButton(MouseButton.Middle, ButtonState.Pressed);
				_wheelDown = true;
			}
			if ((!e.WiimoteState.ButtonState.A || !e.WiimoteState.ButtonState.B) && _wheelDown)
			{
				MouseController.UseButton(MouseButton.Middle, ButtonState.Released);
				_wheelDown = false;
			}

			if (!_wheelDown)
			{
				if (e.WiimoteState.ButtonState.A && !_leftDown)
				{
					MouseController.UseButton(MouseButton.Left, ButtonState.Pressed);
					_leftDown = true;
				}
				if (!e.WiimoteState.ButtonState.A && _leftDown)
				{
					MouseController.UseButton(MouseButton.Left, ButtonState.Released);
					_leftDown = false;
				}

				if (e.WiimoteState.ButtonState.B && !_rightDown)
				{
					MouseController.UseButton(MouseButton.Right, ButtonState.Pressed);
					_rightDown = true;
				}
				if (!e.WiimoteState.ButtonState.B && _rightDown)
				{
					MouseController.UseButton(MouseButton.Right, ButtonState.Released);
					_rightDown = false;
				}
			}

			if (_wheelDown)
			{
				MouseController.MoveBy(_delta.X, 0);
				MouseController.MoveWheelBy(-_delta.Y);
			}
			else
			{
				MouseController.MoveBy(_delta.X, _delta.Y);
			}

			_lastButtonState = _wiimote.WiimoteState.ButtonState;
		}

		private void WiimoteExtension_Changed(object sender, WiimoteExtensionChangedEventArgs e)
		{
		}

		#endregion
	}
}